Skip to content

test(source/cloud-sql-mysql): create MCP integration tests#2922

Open
dishaprakash wants to merge 31 commits intomainfrom
mcp-cloud-sql-mysql-tests
Open

test(source/cloud-sql-mysql): create MCP integration tests#2922
dishaprakash wants to merge 31 commits intomainfrom
mcp-cloud-sql-mysql-tests

Conversation

@dishaprakash
Copy link
Copy Markdown
Contributor

@dishaprakash dishaprakash commented Apr 1, 2026

Description

This PR migrates the Cloud SQL MySQL integration tests to use the /mcp endpoint on the MCP Toolbox Server.

Key Changes

  • tests/mcp_tool.go:

    • Added modular builder functions (Base, ExecuteSQL, TemplateParams) to dynamically generate expected MCP manifests.
    • Added concatenation of individual MCP text blocks into a fully formatted JSON array whenever the test expects one, preserving compatibility with previous test assertions.
  • cloud_sql_mysql_mcp_test.go: Migrated legacy /api tests to /mcp

  • cloud_sql_mysql_create_instance_mcp_test.go: Migrated create-instance tests to validate MCP JSON-RPC handling.

  • tests/options.go: Added functional options for the various MySQL test fixtures to provide an MCP tests specific path

  • tests/tool.go: Used the above mentioned options to allow calling MCP Endpoints in the test fixtures of MySQL

@dishaprakash dishaprakash changed the base branch from main to anubhav-feat-native-mcp-alloydb April 1, 2026 16:59
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces comprehensive integration tests for the Model Context Protocol (MCP) across various data sources, including AlloyDB, Cloud SQL MySQL, and HTTP. It also adds modular helper functions in tests/mcp_tool.go to facilitate standardized MCP tool testing. Several issues were identified in the review: a critical flaw in the Cloud SQL MySQL instance creation test where mocking http.DefaultClient fails to affect the separate toolbox process, and multiple violations of the repository style guide regarding tool naming conventions (snake_case and product name exclusion). Additionally, the use of time.Sleep in MySQL tests was flagged as a potential source of flakiness, and some error assertions were noted as being too weak.

@dishaprakash dishaprakash force-pushed the mcp-cloud-sql-mysql-tests branch from db1827c to 7c651a9 Compare April 1, 2026 17:00
@anubhav756 anubhav756 force-pushed the anubhav-feat-native-mcp-alloydb branch from 69bb59b to d81edf8 Compare April 1, 2026 17:03
@dishaprakash dishaprakash changed the title Mcp cloud sql mysql tests test(source/cloud-sql-mysql): create MCP integration tests Apr 1, 2026
@dishaprakash dishaprakash force-pushed the mcp-cloud-sql-mysql-tests branch from 94d7fce to e3fa5a6 Compare April 1, 2026 21:32
@anubhav756 anubhav756 force-pushed the anubhav-feat-native-mcp-alloydb branch from d81edf8 to 4c21e3f Compare April 2, 2026 10:00
@dishaprakash dishaprakash force-pushed the mcp-cloud-sql-mysql-tests branch from 0019b41 to 0a48e1c Compare April 2, 2026 12:38
@anubhav756 anubhav756 force-pushed the anubhav-feat-native-mcp-alloydb branch 11 times, most recently from 16be479 to f643196 Compare April 4, 2026 13:13
@dishaprakash dishaprakash force-pushed the mcp-cloud-sql-mysql-tests branch from 082b05d to 225d74f Compare April 6, 2026 09:38
@anubhav756 anubhav756 force-pushed the anubhav-feat-native-mcp-alloydb branch from f643196 to 2e6e044 Compare April 7, 2026 07:25
@dishaprakash dishaprakash marked this pull request as ready for review April 7, 2026 07:31
@dishaprakash dishaprakash requested review from a team as code owners April 7, 2026 07:31
@anubhav756 anubhav756 force-pushed the anubhav-feat-native-mcp-alloydb branch from 2e6e044 to 5a9f6bd Compare April 7, 2026 07:32
@dishaprakash dishaprakash force-pushed the mcp-cloud-sql-mysql-tests branch from e698ea1 to 072f36b Compare April 7, 2026 08:11
@anubhav756 anubhav756 force-pushed the anubhav-feat-native-mcp-alloydb branch from 5a9f6bd to d2bdddd Compare April 7, 2026 08:17
Base automatically changed from anubhav-feat-native-mcp-alloydb to main April 7, 2026 08:42
@dishaprakash dishaprakash force-pushed the mcp-cloud-sql-mysql-tests branch from 1aa40c7 to 0910acf Compare April 9, 2026 22:22
@dishaprakash dishaprakash changed the base branch from anubhav-mcp-alloydbpg to main April 9, 2026 22:22
@dishaprakash dishaprakash added the tests: run Label to trigger Github Action tests. label Apr 9, 2026
errText = mcpResp.Error.Message
} else if mcpResp.Result.IsError {
for _, content := range mcpResp.Result.Content {
if content.Type == "text" {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably don't need to check this since we only support text.

Comment on lines +440 to +442
if tc.wantBody == "" {
return
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for mcp's cases, we might not want to skip these? seems like these are the ones that have content.IsError = true and might have a content.Text value? if needed we can add more field to the test cases struct such as wantContentErr string and set those as the string value.

supportArrayParam bool
supportClientAuth bool
supportSelect1Auth bool
IsMCP bool
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
IsMCP bool
isMCP bool

shouldn't need to be exported. User should always set it with the function.

Comment on lines +431 to +438
wantStatus := tc.wantStatusCode
// MCP might return 200 OK for some error cases that REST returns 401
if wantStatus == http.StatusUnauthorized && mcpStatusCode == http.StatusOK {
wantStatus = http.StatusOK
}
if mcpStatusCode != wantStatus {
t.Errorf("StatusCode mismatch: got %d, want %d", mcpStatusCode, wantStatus)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the mcp status is always StatusOK. Lets just check if its statusOK. If there are test cases thats not statusOK, we can add a tc.wantStatusCodeMCP.

Suggested change
wantStatus := tc.wantStatusCode
// MCP might return 200 OK for some error cases that REST returns 401
if wantStatus == http.StatusUnauthorized && mcpStatusCode == http.StatusOK {
wantStatus = http.StatusOK
}
if mcpStatusCode != wantStatus {
t.Errorf("StatusCode mismatch: got %d, want %d", mcpStatusCode, wantStatus)
}
if mcpStatusCode != http.StatusOK {
t.Error("expected status ok")
}

Comment on lines +482 to +500
gotObj := getMCPResultText(t, mcpResp)
gotBytes, _ := json.Marshal(gotObj)
gotStr := string(gotBytes)

if strings.HasPrefix(strings.TrimSpace(tc.wantBody), "[") || strings.HasPrefix(strings.TrimSpace(tc.wantBody), "{") {
// It looks like JSON, let's do JSON comparison
var gotJSON, wantJSON any
_ = json.Unmarshal([]byte(gotStr), &gotJSON)
_ = json.Unmarshal([]byte(tc.wantBody), &wantJSON)

if got != tc.wantBody {
t.Fatalf("unexpected value: got %q, want %q", got, tc.wantBody)
if diff := cmp.Diff(wantJSON, gotJSON); diff != "" {
t.Fatalf("unexpected JSON value mismatch (-want +got):\n%s\nRaw got: %s\nRaw want: %s", diff, gotStr, tc.wantBody)
}
} else {
// Plain string, use strings.Contains as suggested by user
if !strings.Contains(gotStr, tc.wantBody) {
t.Fatalf(`expected %q to contain %q`, gotStr, tc.wantBody)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comparison should be the same as the api endpoint? since they compare direct got != tc.wantBody, we should be able to do the same here too? rather than converting it to type any.

if !ok {
t.Fatalf("unable to find result in response body")
}
// Extract error text if any
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the checks under here, we can check the Content.Text together regardless if it's "IsError".

E.g.

// first parse the content.Text to string
// next check if isError is correct
isError := tc.wantContentErr != ""
if isError != mcpResp.Result.IsError {
    // throw t.Fatalf here
}

// check the content.Text string.
if isError {
    // check content.Text with tc.wantContentErr
} else {
    // check content.Text with tc.wantBody
}

if resp.StatusCode != tc.wantStatusCode {
t.Errorf("StatusCode mismatch: got %d, want %d. Response body: %s", resp.StatusCode, tc.wantStatusCode, string(respBody))
}
if configs.IsMCP {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did a quick draft --
we'll need to add wantContentErr string to the test case struct.

if configs.IsMCP {
				// Invoke the tool via MCP protocol
				mcpStatusCode, mcpResp, err := InvokeMCPTool(t, tc.toolName, tc.args, tc.requestHeader)
				if err != nil {
					t.Fatalf("native error executing %s: %s", tc.toolName, err)
				}
				if mcpStatusCode != http.StatusOK {
					t.Fatalf("expected status ok")
				}

				if tc.wantContentErr != "" {
					AssertMCPError(t, mcpResp, tc.wantContentErr)
					return
				}
				if mcpResp.Result.IsError {
					t.Fatalf("%s returned error result: %v", tc.toolName, mcpResp.Result)
				}
				gotObj := getMCPResultText(t, mcpResp)
				gotBytes, err := json.Marshal(gotObj)
				if err != nil {
					t.Fatalf("error marshaling result object")
				}
				if string(gotBytes) != tc.wantBody {
					var gotJSON, wantJSON any
					errGot := json.Unmarshal(gotBytes, &gotJSON)
					errWant := json.Unmarshal([]byte(tc.wantBody), &wantJSON)

					if errGot == nil && errWant == nil {
						if diff := cmp.Diff(wantJSON, gotJSON); diff != "" {
							t.Fatalf("unexpected JSON value mismatch (-want +got):\n%s\nRaw got: %s\nRaw want: %s", diff, got, tc.wantBody)
						}
					} else {
						t.Fatalf("unexpected value: got %q, want %q", got, tc.wantBody)
					}
				}
			} else {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release candidate Use label to signal PR should be included in the next release. tests: run Label to trigger Github Action tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants